#include "config.h"

#include "ms2sim.h"

ECMBOOL		ignore_loss_violation_onstart;
ecm_double	allowed_loss_ratio;
ECMBOOL		allowed_loss_ratio_violated;

static LIST_HEAD(serviced_sessions);

static void
insert_session(session_t *session)
{
	session_t	*lsession;
	struct list_head	*lp;

	list_for_each (lp, &serviced_sessions) {
		lsession = list_entry(lp, session_t, list);
		if (session->ts < lsession->ts) {
			list_add_tail(&session->list, &lsession->list);
			return;
		}
	}

	list_add_tail(&session->list, &serviced_sessions);
}

void
put_session(session_t *session)
{
	ECMASSERT(list_empty(&session->list));
	ECMASSERT(session->refcnt > 0);

	session->refcnt--;

	if (session->state == STATE_DONE) {
		if (session->refcnt == 0) {
			free_session(session);
		}
	}
	else {
		insert_session(session);
	}
}

ecm_uint64
probe_service_system(ecm_uint64 ts)
{
	session_t	*session;

	if (list_empty(&serviced_sessions))
		return TS_MAX;

	session = list_entry(serviced_sessions.next, session_t, list);
	return session->ts;
}

static session_t *
get_first_session(void)
{
	session_t	*session;

	if (list_empty(&serviced_sessions))
		return NULL;

	session = list_entry(serviced_sessions.next, session_t, list);
	list_del_init(&session->list);
	session->refcnt++;

	return session;
}

static void
cleanup_bcaches(session_t *session)
{
	bcache_t	*bcache;
	ecm_uint32	i;

	for (i = 0; i < session->n_io_blks; i++) {
		bcache = session->bcaches[i];
		if (bcache)
			put_bcache(session, bcache);
	}
	ecm_free(session->bcaches);
	session->bcaches = NULL;
}

static ecm_uint32
get_session_bytes_buffered(session_t *session)
{
	ecm_uint32	bytes_buffered;

	bytes_buffered = session->n_io_blks * cache_blksize;
	if (session->bid + session->n_io_blks - 1 == session->media->bid_max) {
		/* last block */
		ecm_uint32	remain = session->media->size % cache_blksize;

		if (remain > 0)
			bytes_buffered -= (cache_blksize - remain);
	}
	return bytes_buffered;
}

static ECMBOOL
check_deadline(session_t *session, ecm_uint64 ts)
{
	ecm_uint32	n_lost;
	ecm_uint32	dropped;

	if (session->ts_deadline == 0)
		session->ts_deadline = ts + session->usec_round;

	if (session->ts_deadline >= ts)
		return ECM_TRUE;

	cleanup_bcaches(session);

	n_lost = (ts - session->ts_deadline) / session->usec_round;
	n_lost++;
	if (is_stat_started) {
		session->n_lost_rounds += n_lost;
		session->n_rounds += n_lost;
	}

	session->ts = ts;
	session->ts_deadline += (n_lost * session->usec_round);
	session->usec_playing_pos += (n_lost * session->usec_round);

	dropped = session->bytes_buffered + (n_lost - 1) * session->bytes_round;
	session->offset_buff = dropped % cache_blksize;

	session->bid += (dropped / cache_blksize);
	session->bytes_buffered_client = 0;
	session->bytes_buffered = 0;
	session->n_io_blks = 0;

	if (session->bid > session->media->bid_max)
		set_session_state(session, STATE_DONE);
	else
		set_session_state(session, STATE_IDLE);

	/* check loss ratio allowed */
	if (allowed_loss_ratio < 100 && allowed_loss_ratio > 0 &&
	    (is_stat_started || !ignore_loss_violation_onstart)) {
		ecm_double	loss_ratio = calc_loss_ratio();

		if (loss_ratio > allowed_loss_ratio) {
			ERR("packet loss happened: %lf > %lf", loss_ratio, allowed_loss_ratio);
			allowed_loss_ratio_violated = ECM_TRUE;
			ecm_kill_process(ecm_getpid());
		}
	}

	return ECM_FALSE;
}

static ECMBOOL
load_media_block(session_t *session, ecm_uint32 i)
{
	bcache_t	*bcache;
	ecm_uint32	bid = session->bid + i;
	ECMBOOL		res;

	if ((bcache = session->bcaches[i]) != NULL) {
		ECMASSERT(bcache->valid);
		return ECM_TRUE;
	}

	bcache = get_bcache(session, bid);
	ECMASSERT(bcache != NULL);
	
	if (bcache->valid) {
		res = ECM_TRUE;
	}
	else {
		ECMASSERT(session->state == STATE_DISKIO);
		res = ECM_FALSE;
	}

	session->bcaches[i] = bcache;

	return res;
}

static void
load_media_blocks(session_t *session)
{
	ecm_uint32	i;

	if (session->n_io_blks == 0) {
		session->n_io_blks = (session->bytes_round - session->bytes_buffered_client + cache_blksize - 1) / cache_blksize;
		if (session->bid + session->n_io_blks - 1 > session->media->bid_max)
			session->n_io_blks = session->media->bid_max - session->bid + 1;
		session->bytes_buffered = get_session_bytes_buffered(session);
	}

	if (session->bcaches == NULL) {
		session->bcaches = (bcache_t **)ecm_calloc(session->n_io_blks, sizeof(bcache_t *));
		ECMASSERT(session->bcaches != NULL);
	}

	for (i = 0; i < session->n_io_blks; i++) {
		if (!load_media_block(session, i))
			return;
	}

	set_session_state(session, STATE_LOADED);
	put_session(session);
}

void
schedule_service_system(ecm_uint64 ts)
{
	session_t	*session;

	session = get_first_session();
	ECMASSERT(session != NULL);

	DBG(DBG_SERVICE, "schedule: ts:%s, session:[%s]", dbg_timestep(ts), dbg_session(session));

	if (!check_deadline(session, ts)) {
		put_session(session);
		return;
	}

	switch (session->state) {
	case STATE_IDLE:
		if (session->ts_sesslen > 0 && session->ts_sesslen <= ts) {
			set_session_state(session, STATE_DONE);
			put_session(session);
		}
		else {
			try_to_seek_session(session, ts);
			load_media_blocks(session);
		}
		break;
	case STATE_LOADED: {
		ecm_uint32	bytes_buffered;

		invoke_network_IO(session);

		cleanup_bcaches(session);

		session->ts = session->ts_deadline + 1;
		session->ts_deadline += session->usec_round;
		if (is_stat_started)
			session->n_rounds++;

		bytes_buffered = session->bytes_buffered_client + session->bytes_buffered;
		if (bytes_buffered >= session->bytes_round)
			session->bytes_buffered_client = bytes_buffered - session->bytes_round;
		else
			session->bytes_buffered_client = 0;
		session->bid += session->n_io_blks;
		session->usec_playing_pos += session->usec_round;

		session->bytes_buffered = 0;
		session->n_io_blks = 0;

		if (session->bid > session->media->bid_max)
			set_session_state(session, STATE_DONE);
		else
			set_session_state(session, STATE_IDLE);

		put_session(session);
		break;
	}
	default:
		ECMASSERT(0);
		break;
	}
}

void
add_service_session(session_t *session)
{
	insert_session(session);
}
